﻿using UnityEngine;
using System.Collections;

public class GLRenderCanvas : MonoBehaviour
{
	public enum PowerOfTwoSize
	{
		_256 = 256,
		_512 = 512,
		_1024 = 1024,
		_2048 = 2048,
	}

	// Properties

	[SerializeField]
	bool m_usingRendererAttached = true;	// 이 컴포넌트가 붙어있는 GameObject의 Renderer에 바로 RenderTexture를 적용한다
	[SerializeField]
	PowerOfTwoSize m_canvasWidth;					// 캔버스 X
	[SerializeField]
	PowerOfTwoSize m_canvasHeight;					// 캔버스 Y

	[SerializeField]
	Texture2D m_brush;						// 브러시로 사용할 텍스쳐
	[SerializeField]
	Color m_brushColor;					// 브러시 색상
	[SerializeField]
	float m_brushDensity = 1f;			// 브러시 밀도 (직선 렌더링 시 거리상으로 몇 픽셀마다 브러시를 찍어줄것인지 여부)


	// Members

	RenderTexture m_renderTex;					// RenderTexture (캔버스)
	bool m_renderingLock = false;		// 현재 렌더링중인지

	Rect m_brushRect_Cache;				// 브러시 Rect (캐싱)
	Rect m_brushRect_Source;

	/// <summary>
	/// 렌더링해도 괜찮은지 체크
	/// </summary>
	/// <value><c>true</c> if ok to render; otherwise, <c>false</c>.</value>
	bool okToRender
	{
		get
		{
			if (!m_renderingLock)
			{
				Debug.LogWarning("rendering is not ready!");
			}
			return m_renderingLock;
		}
	}


	/// <summary>
	/// 브러시 색상
	/// </summary>
	/// <value>The color of the brush.</value>
	public Color brushColor
	{
		get
		{
			return m_brushColor;
		}
		set
		{
			m_brushColor = value;
		}
	}

	/// <summary>
	/// 캔버스 가로
	/// </summary>
	/// <value>The width.</value>
	public int width
	{
		get
		{
			return (int)m_canvasWidth;
		}
	}
	/// <summary>
	/// 캔버스 세로
	/// </summary>
	/// <value>The height.</value>
	public int height
	{
		get
		{
			return (int)m_canvasHeight;
		}
	}


	//초기화
	void Awake()
	{
		m_renderTex = new RenderTexture((int)m_canvasWidth, (int)m_canvasHeight, 16, RenderTextureFormat.ARGB32);
		SetupForBrush();						// 브러시 초기화 

		if (m_usingRendererAttached)				// * Renderer에 적용 플래그가 켜져있으면 바로 지정해준다
		{
			renderer.material.mainTexture = m_renderTex;
		}
	}

	/// <summary>
	/// 브러시 관련된 초기화 (만약 중간에 브러시 텍스쳐를 바꾼다면 이것도 다시 콜해줘야함)
	/// </summary>
	void SetupForBrush()
	{
		float width = (float)m_brush.width;
		float height = (float)m_brush.height;
		m_brushRect_Cache = new Rect(-width / 2f, -height / 2f, width, height);
		m_brushRect_Source = new Rect(0, 0, 1, 1);
	}

	// 파괴시
	void OnDestroy()
	{
		RenderTexture.active = null;

		m_renderTex.Release();					// 파괴시 RenderTexture 정리해준다
		m_renderTex = null;
	}


	/// <summary>
	/// 캔버스 클리어
	/// </summary>
	/// <param name="clearColor">Clear color.</param>
	public void Clear(Color clearColor)
	{
		bool oldLock = m_renderingLock;
		if (!oldLock)
			StartRender();

		GL.Clear(true, true, clearColor);

		if (!oldLock)
			EndRender();
	}

	/// <summary>
	/// 다른 캔버스에 RenderTexture 복사하기
	/// </summary>
	/// <param name="other">Other.</param>
	public void CopyTo(GLRenderCanvas other)
	{
		Graphics.Blit(m_renderTex, other.m_renderTex);
	}

	/// <summary>
	/// 렌더링 모드 시작
	/// </summary>
	public void StartRender()
	{
		if (m_renderingLock)
		{
			Debug.LogWarning("Rendering already started!");
		}
		else
		{
			m_renderingLock = true;

			RenderTexture.active = m_renderTex;							// 이 오브젝트가 가지고 있는 렌더 텍스쳐로 활성화 (여기에 그린다)
			GL.PushMatrix();												// 렌더링용 매트릭스 설정 (픽셀과 1:1 매칭되는 좌표계)
			GL.LoadPixelMatrix(0, (int)m_canvasWidth, (int)m_canvasHeight, 0);
#if UNITY_EDITOR
			GL.Clear(true, false, Color.black);
#endif
		}
	}

	/// <summary>
	/// 렌더링 모드 종료
	/// </summary>
	public void EndRender()
	{
		if (!m_renderingLock)
		{
			Debug.LogWarning("Rendering not started! (or already finished)");
		}
		else
		{
			m_renderingLock = false;

			GL.PopMatrix();													// 기존 렌더링 매트릭스 복원
			RenderTexture.active = null;									// 렌더링 텍스쳐 해제
#if UNITY_EDITOR
			m_renderTex.DiscardContents();
#endif
		}
	}

	/// <summary>
	/// 좌표에 점 그리기
	/// </summary>
	/// <param name="coord">픽셀과 1:1 매칭되는 좌표계임...</param>
	public void DrawPoint(Vector2 coord)
	{
		if (!okToRender)														// 렌더링 모드인지 체크
			return;
		_DrawPoint(coord);
	}
	void _DrawPoint(Vector2 coord)
	{
		Rect brushRect = new Rect(m_brushRect_Cache);					// 브러시 Rect 복제 후 좌표 수정
		brushRect.center = coord;
		Graphics.DrawTexture(brushRect, m_brush, m_brushRect_Source,
												0, 0, 0, 0, m_brushColor * 0.5f);	// 브러시 텍스쳐 그리기
	}

	/// <summary>
	/// 선 그리기
	/// </summary>
	/// <param name="points">Points.</param>
	public void DrawLine(params Vector2[] points)
	{
		if (!okToRender)														// 렌더링 모드인지 체크
			return;

		int pointCount = points.Length;
		for (int i = 0; i < pointCount - 1; i++)								// 각 점마다 선분 하나씩 그려주기
		{
			_DrawLine(points[i], points[i + 1]);
		}
	}
    public void DrawLine(params HermitPoint[] points)
    {
        if (!okToRender)														// 렌더링 모드인지 체크
            return;

        int pointCount = points.Length;
        for (int i = 0; i < pointCount - 1; i++)								// 각 점마다 선분 하나씩 그려주기
        {
            _DrawLine(points[i].point, points[i + 1].point);
        }
    }
	void _DrawLine(Vector2 start, Vector2 end)
	{
		// TODO : 선을 촘촘하게 그리려고 조금 복잡한 연산을 했는데 최적화할 여지도 많을거같음...

		float dist = Vector2.Distance(start, end);
		int steps = Mathf.CeilToInt(dist / m_brushDensity);	// BrushDensity 거리만큼 점을 찍으려면 해당 거리를 몇 번 나눠 그려야하나...

		Vector2 point;
		for (int i = 0; i <= steps; i++)							// 점 하나씩 그리기
		{
			point = Vector2.Lerp(start, end, (float)i / (float)steps);
			_DrawPoint(point);
		}
	}

    public void DrawCurve(params HermitPoint[] points)
    {
        if (!okToRender)														// 렌더링 모드인지 체크
            return;

        if (points.Length < 3) 
        {
            DrawLine(points);
            return;
        }

        int pointCount = points.Length;

        for (int i = 1; i < pointCount - 1; i++)								// 각 점마다 선분 하나씩 그려주기
        {
            _DrawCurve(points[i], points[i + 1]);
        }
    }

    void _DrawCurve(HermitPoint start, HermitPoint end)
    {
		float dist = Vector2.Distance(start.point, end.point);
		int steps = Mathf.CeilToInt(dist / m_brushDensity);	// BrushDensity 거리만큼 점을 찍으려면 해당 거리를 몇 번 나눠 그려야하나...

		Vector2 point;

        for (int i = 0; i < steps; i++)
        {

            /*
            //헤르밋 보간.
            float scale = (float)i / (float)steps;
            float h1 =  2 * Mathf.Pow(scale, 3) - 3 * Mathf.Pow(scale, 2) + 1;
            float h2 = -2 * Mathf.Pow(scale, 3) + 3 * Mathf.Pow(scale, 2);
            float h3 =      Mathf.Pow(scale, 3) - 2 * Mathf.Pow(scale, 2) + scale;
            float h4 =      Mathf.Pow(scale, 3) -     Mathf.Pow(scale, 2);
            point = h1 * start.point + h2 * end.point + h3 * (start.nextPoint) + h4 * (end.prePoint);
             */

            
            //베지어 커브.
            float scale = (float)i / (float)steps;
            float a = (1 - scale) * (1 - scale) * (1 - scale);
            float b = 3 * scale * (1 - scale) * (1 - scale);
            float c = 3 * scale * scale * (1 - scale);
            float d = scale * scale * scale;
            point = a * start.point + b * start.nextPoint + c*end.prePoint + d*end.point;
            

            _DrawPoint(point);

            /*
            Color oldColor = brushColor;
            brushColor = Color.black;
            DrawPoint(start.point);
            DrawPoint(end.point);

            brushColor = Color.blue;
            DrawPoint(start.nextPoint);

            brushColor = Color.red;
            DrawPoint(end.prePoint);
            brushColor = oldColor;
            */
        }
    }
}




